<template>
    <div style="display: inline-block;" @click="exportBtn">
        <slot />
    </div>
</template>

<script lang="ts">
export default { name: 'ExceljsExport' }
</script>

<script lang="ts" setup>
import { getCurrentInstance, ComponentInternalInstance, reactive, ref, nextTick, onMounted, useAttrs, useSlots } from 'vue'

import ExcelJS from 'exceljs'
import FileSaver from 'file-saver'
import colorTransfer from '@/utils/colorTransfer'
// const slots = useSlots()
// const attrs = useAttrs()
// const emits = defineEmits()
const { proxy } = getCurrentInstance() as ComponentInternalInstance

const props = defineProps({
    config: {
        type: Object,
        default: () => ({
            fileName: 'export',
            sheet: [
                // {
                //     sheetName: 'sheet1',
                //     config: {},
                // },
            ],
        }),
    },
    fileName: {
        type: String,
        default: 'export',
    },
    data: {
        type: Array,
        default: () => [],
    },
    columns: {
        type: Array,
        default: () => [],
    },
    // eslint-disable-next-line vue/require-default-prop
    reference: null,
    headerRowStyle: {
        type: Function,
        default: () => {},
    },
    headerCellStyle: {
        type: Function,
        default: () => {},
    },
    rowStyle: {
        type: Function,
        default: () => {},
    },
    cellStyle: {
        type: Function,
        default: () => {},
    },
    // eslint-disable-next-line vue/require-default-prop
    spanMethod: null,
})
const state = reactive({

})

const excel:any = {
    workbook: {},
    sheets: [],
    exportBy: null, // 1.使用config 2.使用columns 3.使用reference
    columns: [],
    headers: [],
    headerLevel: 1,
    data: [],
    defaultHeaderRowStyle: {},
    defaultHeaderCellStyle: {
        alignment: {
            vertical: 'middle',
            horizontal: 'center',
        },
        fontWeight: 'bold',
        border: 'thin',
    },
    defaultBodyCellStyle: {
        alignment: {
            vertical: 'middle',
            horizontal: 'center',
        },
    },
}

// 字段转换表
// ['excel对应字段', 'element-ui对应字段']
const transMap = {
    // columns
    columns: [
        ['header', 'label'],
        ['key', 'prop'],
    ],
    font: [
        ['name', 'fontFamily'],
        ['size', 'fontSize'],
        ['bold', 'fontWeight'], // normal
        ['italic', 'fontStyle'], // normal
        ['underline', 'textDecoration'], // underline
        ['strike', 'textDecoration'], // line-through
        ['vertAlign', 'verticalAlign'], // 只支持 super，sub
    ],
}
type TransMap = typeof transMap
type keyOfTransMap = keyof TransMap

// 获取对应的转换字段
// type: 0:查找另一个对应的字段，1:查找对应的element-ui字段，2:查找对应的excel字段
// eslint-disable-next-line consistent-return
const getCorrespondProp = (mapName:keyOfTransMap, propName:string, type = 0) => {
    for (let i = 0; i < transMap[mapName].length; i++) {
        const item = transMap[mapName][i]
        const index = item.indexOf(propName)
        if (type === 0 && index !== -1) return item[index === 0 ? 1 : 0] // 查找另一个对应的字段
        if (type === 1 && index === 0) return item[1] // 查找对应的element-ui字段
        if (type === 2 && index === 1) return item[0] // 查找对应的excel字段
    }
}

const createWorkbook = () => {
    excel.workbook = new ExcelJS.Workbook()
    excel.workbook.creator = 'Export'
    excel.workbook.lastModifiedBy = 'Export'
    excel.workbook.created = new Date(1985, 8, 30)
    excel.workbook.modified = new Date()
    excel.workbook.lastPrinted = new Date(2016, 9, 27)
    excel.workbook.views = [
        {
            x: 0,
            y: 0,
            width: 10000,
            height: 20000,
            firstSheet: 0,
            activeTab: 0,
            visibility: 'visible',
        },
    ]
}

const createWorksheet = () => {
    const sheets = excel.exportBy === 1 ? props.config.sheet : [{ sheetName: 'sheet1', config: {} }]
    sheets.forEach((item:any) => {
        const sheet = excel.workbook.addWorksheet(item.sheetName, item.config)
        excel.sheets.push(sheet)
    })
}

const downloadExcel = async() => {
    await excel.workbook.xlsx.writeBuffer().then((buffer:any) => {
        FileSaver.saveAs(new Blob([buffer]), `${props.fileName || props.config.fileName}.xlsx`)
    }).catch((e:any) => {
        // eslint-disable-next-line no-console
        console.warn(`excel导出失败，原因为：${e}`)
    })
}

// 自动列宽
const autoWidth = () => {
    const getLength = (str:string) => {
        if (str) {
            let l = 0
            let cache:any = null
            // eslint-disable-next-line no-control-regex
            cache = str.match(/[\u0000-\u00ff]/g) // 半角
            if (cache) (l += cache.length)
            cache = (str.match(/[\u4e00-\u9fa5]/g)) // 中文
            if (cache) (l += cache.length * 2)
            cache = (str.match(/[\uff00-\uffff]/g)) // 全角
            if (cache) (l += cache.length * 2)
            return l
        }
        return 1
    }
    const isAutoWidth = (column) => {
        const keys = Object.keys(column)
        for (let i = 0; i < keys.length; i++) {
            const key = keys[i]
            if (key.replace(/-/g, '').toLowerCase() === 'exportautowidth') return true
        }
        return false
    }
    excel.columns.forEach((item:any, columnIndex:number) => {
        if (item.exportAutoWidth === false) return
        if (!('width' in item) || isAutoWidth(item)) {
            const cells:any = []
            let max = 11
            // eslint-disable-next-line no-underscore-dangle
            excel.sheets[0]._rows.forEach((item) => {
            // eslint-disable-next-line no-underscore-dangle
                const cell = item._cells[columnIndex]
                cells.push(cell)

                const l = getLength(cell.value)
                const size = cell?.font?.size || 11
                const bold = !!cell?.font?.bold
                const width = (bold ? 5 : 0) + size * l
                if (max < width) max = width
            })
            excel.sheets[0].columns[columnIndex].width = max / 9
        }
    })
}

// 转换列
const transferColumns = (list:any) => {
    const newColumns:any = []
    list.forEach((object:any) => {
        const newObj:any = {}
        Object.keys(object).forEach((oldProp) => {
            const transferProp = getCorrespondProp('columns', oldProp, 2) || oldProp
            newObj[transferProp] = object[oldProp]
            if (transferProp === 'width') newObj.width = Math.ceil(parseInt(object.width, 10) / 9)
        })
        // if ((!('width' in newObj) || 'exportAutoWidth' in newObj) && newObj.key) newObj.width = Math.ceil(getAutoWidth(newObj.key))
        newColumns.push(newObj)
    })
    return newColumns
}

// 生成列
const generateColumns = () => {
    if (excel.exportBy === 1) {
        excel.sheets.forEach((item:any, index:number) => {
            item.columns = props.config.sheet[index].config.columns
        })
    } else if (excel.exportBy === 2) {
        const { headers, columns, level } = getColumnInfoByColumns()
        excel.headers = headers
        excel.columns = columns
        excel.headerLevel = level
        const index = props.columns.findIndex((column:any) => column.label)
        if (index === -1) excel.headerLevel = 0
    } else if (excel.exportBy === 3) {
        const { headers, columns, level } = getColumnInfoByReference()
        excel.headers = headers
        excel.columns = columns
        excel.headerLevel = level
    }
}

// 获取表头以及列ByReference
const getColumnInfoByReference = () => {
    const getColumns = (slot) => {
        let Columns:any = []
        const headers:any = []
        let columns:any = []
        let level = 1
        if (slot.default) {
            Columns = slot.default().filter((item) => (item.type.name === 'EditColumn' || item.type.name === 'ElTableColumn') && item.props.type !== 'selection' && item.props.export !== false)
            Columns.forEach((Column:any) => {
                let column:any = {}
                column = Column.props
                if (Column.children && Column.props.export !== 'slot') {
                    const { headers: children, columns: childrenColumns, level: childrenLevel } = getColumns(Column.children)
                    level += childrenLevel
                    columns = columns.concat(childrenColumns)
                    if (children.length) {
                        column.children = children
                        delete column.prop
                    }
                } else {
                    if (column.prop) column.property = column.prop
                    columns.push({ ...column })
                }
                headers.push({ ...column })
            })
        }
        return { headers, columns, level }
    }
    return getColumns(props.reference.$slots)
}

// 获取表头以及列ByColumns
const getColumnInfoByColumns = () => {
    const getColumns = (propColumns) => {
        let columns:any = []
        let level = 1
        propColumns.forEach((propColumn:any) => {
            if (propColumn.export === false || propColumn.type === 'selection') return
            if (propColumn.children) {
                const { columns: childrenColumns, level: childrenLevel } = getColumns(propColumn.children)
                level += childrenLevel
                columns = columns.concat(childrenColumns)
                if (propColumn.children.length) delete propColumn.prop
            } else {
                if (propColumn.prop) propColumn.property = propColumn.prop
                columns.push({ ...propColumn })
            }
        })
        return { columns, level }
    }
    const { columns, level } = getColumns(props.columns)
    return { headers: props.columns, columns, level }
}

// 处理数据中的formatter
const parseData = (data = props.data) => {
    const newData:any = []
    data.forEach((row:any, rowIndex:number) => {
        const newObj:any = {}
        excel.columns.forEach((column:any) => {
            if ('formatter' in column) {
                newObj[column.prop] = column.formatter(row, column, row[column.prop], rowIndex, 'exportUse')
            } else if (column.type === 'index') {
                column.prop = 'elementRowIndexProp'
                if (!column.index) {
                    newObj[column.prop] = (rowIndex + 1).toString()
                } else {
                    newObj[column.prop] = column.index(rowIndex).toString()
                }
            } else {
                newObj[column.prop] = row[column.prop]
            }
        })
        newData.push(newObj)
    })
    excel.data = newData
}

// 填充数据
const fillData = () => {
    if (excel.exportBy === 1) {
        //
    } else {
        excel.sheets[0].addRows(excel.data)
    }
}

// 颜色转换
const transferColor = (color:string) => {
    if (!color) return
    // eslint-disable-next-line consistent-return
    return colorTransfer(color)[2].replace(/#/g, '')
}
// 将css样式转换为exceljs样式
const transferStyle = (style:any) => {
    if (Object.keys(style).length === 0) return {}
    const newStyle:any = []
    const spliteBorder = (border:string) => {
        const split = border.split(' ') // 1px soild red
        // 第二个参数（soild这个位置）可选参数如下
        // 有效边框样式

        // thin             细实线
        // dotted           虚线
        // dashDot          长短虚线混合
        // hair             细虚线
        // dashDotDot       长短短虚线混合
        // slantDashDot     长短粗虚线混合
        // mediumDashed
        // mediumDashDotDot
        // mediumDashDot
        // medium
        // double
        // thick
        if (split.length === 3) split.shift() // 因为1px设置无效，所以剔除
        return split
    }
    Object.keys(style).forEach((oldProp) => {
        if (oldProp === 'border') {
            if (typeof style[oldProp] === 'string') {
                const split = spliteBorder(style[oldProp])
                newStyle.border = {
                    top: { style: split[0], color: { argb: transferColor(split[1]) } },
                    left: { style: split[0], color: { argb: transferColor(split[1]) } },
                    bottom: { style: split[0], color: { argb: transferColor(split[1]) } },
                    right: { style: split[0], color: { argb: transferColor(split[1]) } },
                }
            }
        } else if (oldProp === 'borderTop') {
            if (typeof style[oldProp] === 'string') {
                const split = spliteBorder(style[oldProp])
                if (!newStyle.border) newStyle.border = {}
                newStyle.border.top = { style: split[0], color: { argb: transferColor(split[1]) } }
            }
        } else if (oldProp === 'borderRight') {
            if (typeof style[oldProp] === 'string') {
                const split = spliteBorder(style[oldProp])
                if (!newStyle.border) newStyle.border = {}
                newStyle.border.right = { style: split[0], color: { argb: transferColor(split[1]) } }
            }
        } else if (oldProp === 'borderBottom') {
            if (typeof style[oldProp] === 'string') {
                const split = spliteBorder(style[oldProp])
                if (!newStyle.border) newStyle.border = {}
                newStyle.border.bottom = { style: split[0], color: { argb: transferColor(split[1]) } }
            }
        } else if (oldProp === 'borderLeft') {
            if (typeof style[oldProp] === 'string') {
                const split = spliteBorder(style[oldProp])
                if (!newStyle.border) newStyle.border = {}
                newStyle.border.left = { style: split[0], color: { argb: transferColor(split[1]) } }
            }
        } else if (oldProp === 'backgroundColor' || oldProp === 'background') { // 设置背景颜色
            newStyle.fill = {
                type: 'pattern',
                pattern: 'solid',
                fgColor: { argb: transferColor(style[oldProp]) },
            }
        } else if (oldProp === 'fontStyle') { // 斜体
            if (style[oldProp] !== 'normal') {
                if (!newStyle.font) newStyle.font = {}
                newStyle.font.italic = true
            }
        } else if (oldProp === 'fontWeight') { // 粗体
            if (style[oldProp] !== 'normal') {
                if (!newStyle.font) newStyle.font = {}
                newStyle.font.bold = true
            }
        } else if (oldProp === 'textDecoration') { // 下划线
            if (style[oldProp] === 'underline') {
                if (!newStyle.font) newStyle.font = {}
                newStyle.font.underline = true
            } else if (style[oldProp] === 'lineThrough') { // 删除线
                if (!newStyle.font) newStyle.font = {}
                newStyle.font.strike = true
            }
        } else if (oldProp === 'verticalAlign') { // 上下对齐
            if (style[oldProp] === 'super' || style[oldProp] === 'sub') {
                if (!newStyle.font) newStyle.font = {}
                newStyle.font.vertAlign = `${style[oldProp]}script`
            }
        } else if (oldProp === 'textAlign') { // 左右对齐
            if (!newStyle.alignment) newStyle.alignment = {}
            newStyle.alignment.horizontal = style[oldProp]
        } else if (oldProp === 'fontFamily') { // 字体
            if (!newStyle.font) newStyle.font = {}
            newStyle.font.name = style[oldProp]
        } else if (oldProp === 'fontSize') { // 大小
            if (!newStyle.font) newStyle.font = {}
            newStyle.font.size = parseInt(style[oldProp], 10)
        } else if (oldProp === 'color') { // 颜色
            if (!newStyle.font) newStyle.font = {}
            newStyle.font.color = { argb: transferColor(style[oldProp]) }
        } else if (oldProp === 'width') { // 宽度
            newStyle[oldProp] = parseInt(style[oldProp], 10)
        } else {
            newStyle[oldProp] = style[oldProp]
        }
    })
    // if (!newStyle.font) newStyle.font = {}
    // if (!newStyle.font.size) newStyle.font.size = 12
    return newStyle
}

// 传入实例（行或单元格），样式，把样式加在实例上
const addStyle = (instance:any, oldStyle:any) => {
    if (Object.keys(oldStyle).length) {
        const newStyle = transferStyle(oldStyle)
        Object.keys(newStyle).forEach((oldProp) => {
            instance[oldProp] = newStyle[oldProp]
        })
    }
}
// 添加表头行样式
const addHeaderRowStyle = () => {
    excel.sheets[0].getRows(1, excel.headerLevel).forEach((header, index) => {
        const values = [...new Set(header.values)].filter(Boolean)
        const oldStyle = props.headerRowStyle({ values, rowIndex: index }, header) || {}
        addStyle(header, { ...excel.defaultHeaderRowStyle, ...oldStyle })
        addHeaderCellStyle(header, index, values)
    })
}
// 添加表头单元格样式
const addHeaderCellStyle = (header:any, rowNumber:number, values:any) => {
    header.eachCell({ includeEmpty: true }, (cell:any, colNumber:number) => {
        const params = {
            values,
            rowIndex: rowNumber,
            value: cell.value,
            // eslint-disable-next-line no-underscore-dangle
            columnIndex: cell._column._number - 1,
        }
        const oldStyle = props.headerCellStyle(params, cell) || {}
        addStyle(cell, { ...excel.defaultHeaderCellStyle, ...oldStyle })
    })
}
// 添加行样式
// function({ row, rowIndex }) / string
const addRowStyle = () => {
    excel.sheets[0].eachRow({ includeEmpty: true }, (row:any, rowNumber:number) => {
        if (rowNumber > excel.headerLevel) {
            const oldStyle = props.rowStyle({ row: excel.data[rowNumber - excel.headerLevel - 1], rowIndex: rowNumber - excel.headerLevel - 1 }, row) || {}
            addStyle(row, oldStyle)
            addCellStyle(row, rowNumber)
        }
    })
}
// 添加单元格样式
// function({ row, column, rowIndex, columnIndex }) / string
const addCellStyle = (row:any, rowNumber:number) => {
    row.eachCell({ includeEmpty: true }, (cell:any, colNumber:number) => {
        const params = {
            row: excel.data[rowNumber - excel.headerLevel - 1],
            rowIndex: rowNumber - excel.headerLevel - 1,
            // eslint-disable-next-line no-underscore-dangle
            column: { property: cell._column._key },
            // eslint-disable-next-line no-underscore-dangle
            columnIndex: cell._column._number - 1,
        }
        const oldStyle = props.cellStyle(params, cell) || {}
        addStyle(cell, { ...excel.defaultBodyCellStyle, ...oldStyle })
    })
}

// 生成多级表头
const generateHeader = () => {
    if (excel.headerLevel === 1) return
    const xPoss:number[] = [0]

    const addMerfeInfo = (list, level) => {
        let childrenCount = 0
        list.forEach((header, index) => {
            if (header.children) {
                const childrenLength = addMerfeInfo(header.children, level + 1)
                childrenCount = header.children.length + (childrenLength ? childrenLength - 1 : 0)
                header.mergeRow = 1
                header.mergeCol = childrenCount
            } else {
                header.mergeRow = excel.headerLevel - level + 1
                header.mergeCol = 1
            }

            header.yPos = level
            header.xPos = (xPoss[level] || 0) + 1
            fillHeader(header)
            xPoss[level] = (xPoss[level] || 0) + header.mergeCol
            for (let i = level + 1; i < header.mergeRow + level; i++) {
                const cols = xPoss[i] || 0
                xPoss[i] = cols + header.mergeCol
            }
        })
        return childrenCount
    }
    addMerfeInfo(excel.headers, 1)
}

// 填充表头
const fillHeader = (header:any) => {
    excel.sheets[0].mergeCells(header.yPos, header.xPos, header.yPos + header.mergeRow - 1, header.xPos + header.mergeCol - 1)
    const cell = excel.sheets[0].getRow(header.yPos).getCell(header.xPos)
    cell.value = header.label
}

// 合并单元格
const mergeCells = () => {
    if (!props.spanMethod) return
    excel.columns.forEach((column, columnIndex) => {
        excel.data.forEach((row, index) => {
            const rowIndex = index + excel.headerLevel
            let mergeResult = props.spanMethod({ row, column, rowIndex: index, columnIndex })
            if (mergeResult) {
                if (Array.isArray(mergeResult)) {
                    mergeResult = {
                        rowspan: mergeResult[0],
                        colspan: mergeResult[1],
                    }
                }
                const mergeList = [rowIndex + 1, columnIndex + 1, rowIndex + mergeResult.rowspan, columnIndex + mergeResult.colspan]

                let shouldWarn = false
                if (mergeList[0] > mergeList[2] || mergeList[1] > mergeList[3]) shouldWarn = true
                try {
                    // 按开始行，开始列，结束行，结束列合并
                    excel.sheets[0].mergeCells(...mergeList)
                } catch (e) {
                    if (e.toString() === 'Error: Cannot merge already merged cells') {
                        shouldWarn = false
                    } else {
                        throw new Error(e)
                    }
                }
                if (shouldWarn) console.warn('[TableExport Warn] 在反向合并单元格或删除单元格时可能会与Element UI的效果不同！')
            }
        })
    })
}

const reset = () => {
    excel.workbook = {}
    excel.sheets = []
    excel.exportBy = null
    excel.columns = []
    excel.headers = []
    excel.headerLevel = 1
    excel.data = []
}

const exportBtn = () => {
    if (props.reference) exportByReference()
    else if (props.columns.length) exportByColumns()
    else exportByConfig()
}

const exportByConfig = (data?:any[]) => {
    reset()
    excel.exportBy = 1
    exportExcel(data)
}
const exportByColumns = (data?:any[]) => {
    reset()
    excel.exportBy = 2
    exportExcel(data)
}
const exportByReference = (data?:any[]) => {
    reset()
    excel.exportBy = 3
    exportExcel(data)
}

const exportExcel = (data?:any[]) => {
    createWorkbook()
    createWorksheet()
    generateColumns()
    parseData(data)
    excel.sheets[0].columns = transferColumns(excel.columns)

    generateHeader()
    fillData()
    addHeaderRowStyle()
    addRowStyle()
    autoWidth()
    mergeCells()
    downloadExcel()
    reset()
}
defineExpose({
    exportByConfig,
    exportByColumns,
    exportByReference,
    // excel,
    // createWorkbook,
    // createWorksheet,
    // generateColumns,
    // fillData,
    // addRowStyle,
    // mergeCells,
    // downloadExcel,
})
</script>
<style lang="scss" scoped>

</style>
